package com.xmler.plugin.adbrocket.shell

import com.android.ddmlib.IDevice
import com.google.common.base.Joiner
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.project.Project
import com.xmler.plugin.adbrocket.ObjectGraph
import com.xmler.plugin.adbrocket.bean.ProcessBean
import com.xmler.plugin.adbrocket.utils.AdbUtil
import com.xmler.plugin.adbrocket.utils.Logger
import com.xmler.plugin.adbrocket.utils.ReflectHelper
import org.jetbrains.android.sdk.AndroidSdkUtils
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import kotlin.Comparator

object ShellFacade {

    private var mAdbOsLcation: String? = null


    //=======================================================
    // =================  公共方法  ==========================
    //=======================================================
    /**
     * 重启adb server
     */
    fun restartAdbServer(project: Project) {
        if (AdbUtil.isGradleSyncInProgress(project)) {
            //
            return
        }
        WriteCommandAction.runWriteCommandAction(project) {
            try {
                val ret = project.getComponent(ObjectGraph::class.java)
                    .bridge
                    .mRealBridge
                    .restart(10L, TimeUnit.SECONDS)
                Logger.d("Adb restart > $ret")
            } catch (e: Exception) {
                Logger.e("Adb restart error: $e")
            }
        }
    }

    /**
     * 获取设备上面debuggable的进程列表
     */
    fun queryAllDebugableProcesses(project: Project, device: IDevice?): List<ProcessBean> {
        Logger.d("Query all debuggable processes...")
        val pidResult = execute(project, device, "jdwp")
        if (pidResult.status != 0){
            return emptyList()
        }
        val pids = Joiner.on("|").join(pidResult.output)
        val pkgResult = execute(project, device,
            "shell ps | grep -E \"^[^[:space:]]+[[:space:]]+($pids)\"")
        if (pkgResult.status != 0) {
            return emptyList()
        }

        //
        val regPid = """(?<=\s{1,20})\d+""".toRegex()
        val regPkg = """(?<=\s{1,20})\S*?$""".toRegex()
        val pkgs = ArrayList<ProcessBean>()
        pkgResult.output.forEach { line ->
            val pid = regPid.find(line)?.let {
                it.value.toInt()
            } ?: 0

            val pkg = regPkg.find(line)?.let {
                it.value
            } ?: ""

            val bean = ProcessBean(pid, pkg)
            Logger.d(bean.toString())
            pkgs.add(bean)
        }
        pkgs.sortWith(Comparator { a, b ->
            if (a.packageName.isNullOrEmpty() || b.packageName.isNullOrEmpty())
                0
            a.packageName!!.compareTo(b.packageName!!)
        })
        return pkgs
    }

    fun getWlanIpAddr(project: Project, device: IDevice?): String? {
        val shellReaderResult = execute(project, device, "shell ifconfig wlan0 | head -n 3")
        if (shellReaderResult.status == 0) {
            val regex = """(?<=inet addr:)[\d\.]+""".toRegex()
            shellReaderResult.output.find { it.contains("inet addr:") }?.let { line ->
                regex.find(line)?.let {
                    return it.value
                }
            }
        }
        return ""
    }


    /**
     * @param cmd 不包含adb，内部会自动找到当前系统存在的adb
     */
    fun execute(project: Project, device: IDevice?, cmd: String): ShellReaderResult {
        device ?: return ShellReaderResult(-1, ArrayList())
        return execute(project, device, cmd, 2L, TimeUnit.SECONDS)
    }

    //=======================================================
    // =================  私有方法  ==========================
    //=======================================================
    private fun getAdbLaunchCommand(device: IDevice?, cmd: String): List<String> {
        val cmds = ArrayList<String>()
        cmds.add(mAdbOsLcation!!)
        device?.let {
            cmds.add("-s")
            cmds.add(it.serialNumber)
        }

        // 参考Runtime的exec方法，将cmd指令分割然后传给ProcessBuilder执行
        // 否则当cmd=shell ps复合指令时总是提示adb找不到命令"shell ps"
        val st = StringTokenizer(cmd)
        val cmdarray = ArrayList<String>()
        while (st.hasMoreTokens()){
            cmdarray.add(st.nextToken())
        }
        cmds.addAll(cmdarray)
        return cmds.toList()

    }

    private fun execute(project: Project, device: IDevice?, cmd: String, timeout: Long, unit: TimeUnit): ShellReaderResult {
        if (mAdbOsLcation.isNullOrEmpty()) {
            val sdk = AndroidSdkUtils.getAdb(project)
            if (sdk == null) {
                Logger.e("Unable to locate adb.")
                return ShellReaderResult(-1, ArrayList())
            }
            mAdbOsLcation = sdk!!.path
            Logger.d("Initializing adb using: $mAdbOsLcation")
        }


        val cmds = getAdbLaunchCommand(device, cmd)
        val cmdResult: Future<ShellReaderResult> = ApplicationManager
            .getApplication()
            .executeOnPooledThread(ShellReaderTask(project, cmds, timeout, unit))
        return cmdResult.get()
    }

    class ShellReaderTask(val project: Project, val cmds: List<String>, private val timeout: Long, private val unit: TimeUnit): Callable<ShellReaderResult> {

        override fun call(): ShellReaderResult {
            val startTime = System.currentTimeMillis()
            var status = -1
            val commandString = Joiner.on(' ').join(cmds)
            Logger.d("Launching '%s' to ensure ADB is running.", commandString)
            try {
                val pb = ProcessBuilder(cmds).directory(null)
                pb.environment().clear()

                val proc = pb.start()
                val errorOutput = ArrayList<String>()
                val stdOutput = ArrayList<String>()
                status = ReflectHelper.reflectAdbProcessOutput(proc, errorOutput, stdOutput, true, timeout, unit)
                Logger.d("Used time: ${(System.currentTimeMillis() - startTime) / 1000}s ")
                Logger.d("status: $status")
                Logger.d("output: ")
                stdOutput.forEach { Logger.d(">> $it") }

                Logger.d("error: ")
                errorOutput.forEach { Logger.d(">> $it") }

                Logger.d("")
                return if (stdOutput.isNotEmpty()) {
                    ShellReaderResult(0, stdOutput)
                } else {
                    ShellReaderResult(-1, errorOutput)
                }
            } catch (e: Exception) {
                Logger.e("ShellFacade.execute error: $e")
            }
            return ShellReaderResult(status, ArrayList())
        }

    }

    class ShellReaderResult(val status: Int, val output: ArrayList<String>)
}